/*
 * Created on 2004-01-29
 */
package org.opencube.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.opencube.data.BoundVariable;





/**
 * The meta data of the statement parameters. 
 * @author <a href="mailto:maciek@fingo.pl">FINGO - Maciej Mroczko</a>
 * TODO: comment
 */
public class ParameterMetaData
{
	/* The valid parameter delimiters */
	private static final String PARAMETER_DELIMITERS = " \t\n-+*/<>=|():,;";
	/* The string literal delimiter */
	private static final String LITERAL_DELIMITER = "'";
	/* The statement */
	private String statement = null;
	/* The parameter_name -> value map */
	private BoundVariable[] bindVariables = null;
	/* Parameters */
	private Parameter[] parameters = null;		
	
	/**
	 * Constructor.
	 * 
	 * @param database
	 * @param statement
	 * @param bindVariables
	 */
	public ParameterMetaData( String statement, BoundVariable[] bindVariables )
	{
		this.statement = statement;
		this.bindVariables = bindVariables;
		parseParams();
	}
	
	/**
	 * Return parameters.
	 * 
	 * @return BoundVariable[]
	 */
	public Parameter[] getParameters()
	{
		return this.parameters;
	}
	
	/**
	 * Returns input parameters.
	 * 
	 * @return BoundVariable[]
	 */
	public Parameter[] getInputParameters()
	{
		return getByMode( Parameter.MODE_IN );
	}
	
	/**
	 * Returns input parameters.
	 * 
	 * @return BoundVariable[]
	 */
	public Parameter[] getOutputParameters()
	{
		return getByMode( Parameter.MODE_OUT );
	}

	/* Returns all parameters with the given mode */
	private Parameter[] getByMode( String mode )
	{
		ArrayList result = new ArrayList();
		for( int i = 0; parameters != null && i < parameters.length; i++ )
		{
			if( mode.equals( parameters[ i ].getMode() ) )
			{
				result.add( parameters[ i ] );
			}
		}
		
		return (Parameter[]) result.toArray( new Parameter[ result.size() ] );
	}
	
	/* Parses the statement ad searches for indexes of the parameters */
	private void parseParams()
	{
		String[] indexes = new String[ statement.length() ];
		for( int i = 0; bindVariables != null && i < bindVariables.length; i++ )
		{
			String paramName = bindVariables[ i ].getName();
			int index = -1;
			while( ( index = statement.indexOf( paramName, index + 1 ) ) != -1 )
			{
				if( isValidParameter( paramName, index ) )
				{
					indexes[ index ] = paramName;
				}
			}
		}
		
		HashMap params = new HashMap();
		int index = 0;
		for( int i = 0; i < indexes.length; i++ )
		{
			if( indexes[ i ] != null )
			{
				index++;
				ArrayList array = (ArrayList) params.get( indexes[ i ] );
				if( array == null )
				{
					array = new ArrayList();
					params.put( indexes[ i ], array );
				}
				array.add( new Integer( index ) );
			}
		}
		this.parameters = new Parameter[ params.size() ];
		Iterator it = params.keySet().iterator();
		for( int i = 0; i < this.parameters.length; i++ )
		{
			String name = (String) it.next();
			ArrayList array = (ArrayList) params.get( name ); 
			int[] paramIndexes = new int[ array.size() ];
			for( int j = 0; j < array.size(); j++ )
			{
				paramIndexes[ j ] = ( (Integer) array.get( j ) ).intValue();
			}
			this.parameters[ i ] = new Parameter( name, getBoundVariable( name ).getValue(), paramIndexes );
		}
	}
	
	/* Checks, if the given character is correct parameter name delimiter */
	private boolean correctDelimiter( char c )
	{
		return PARAMETER_DELIMITERS.indexOf( c ) != -1;
	}
	
	/* Checks, if the string the the given index is part of the literal */
	private boolean inLiteral( int index )
	{
		int delimiterIndex = -1;
		int counter = 0;
		String string = statement.substring( 0, index );
		while( ( delimiterIndex = string.indexOf( LITERAL_DELIMITER, delimiterIndex + 1 ) ) != -1 )
		{
			counter++;
		}
		
		return counter % 2 != 0;
	}
	
	/* Checks, if the string at the given index is valid parameter name */
	private boolean isValidParameter( String name, int index )
	{
		// char before name
		if( index > 0 && !correctDelimiter( statement.charAt( index - 1 ) ) )
		{
			return false;
		}
		// char after name
		if( statement.length() > index + name.length() && !correctDelimiter( statement.charAt( index + name.length() ) ) )
		{
			return false;
		}
		
		// not literal?
		return !inLiteral( index );
	} 
	
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		sb.append( "statement: " + statement + Constants.STR_NEW_LINE );
		sb.append( "parameters: " );
		for( int i = 0; parameters != null && i < parameters.length; i++ )
		{
			sb.append( Constants.STR_NEW_LINE + Constants.STR_NEW_LINE );
			sb.append( parameters[ i ] );
		}
		
		return sb.toString();
	}
	
	/**
	 * Returns the bound variable with the given name
	 * 
	 * @param name
	 * @return
	 */
	public BoundVariable getBoundVariable( String name )
	{
		for( int i = 0; bindVariables != null && i < bindVariables.length; i++ )
		{
			if( name.equals( bindVariables[ i ].getName() ) )
			{
				return bindVariables[ i ];
			}
		}
		
		return null;
	}
}